{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 题目\n",
    "设计一个简历类，必须有姓名，可以设置性别和年龄，即个人信息，可以设置曾就职公司和工作时间，即工作经历。\n",
    "\n",
    "## 基础版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "鸣人\t男\t29\n",
      "2016-2018\t木叶公司\n",
      "鸣人\t男\t29\n",
      "2016-2018\t木叶公司\n",
      "鸣人\t男\t29\n",
      "2016-2018\t木叶公司\n"
     ]
    }
   ],
   "source": [
    "class Resume():\n",
    "    \n",
    "    def __init__(self, name):\n",
    "        self.name = name  # python默认成员变量公开\n",
    "        self.__sex = None  # python默认成员变量公开，加__表示私有\n",
    "        self.__age = None  # python默认成员变量公开，加__表示私有\n",
    "        self.__time_area = None  # python默认成员变量公开，加__表示私有\n",
    "        self.__company = None  # python默认成员变量公开，加__表示私有\n",
    "        \n",
    "    def set_personal_info(self, sex, age):\n",
    "        self.__sex = sex\n",
    "        self.__age = age\n",
    "    \n",
    "    def set_work_experience(self, time_area, company):\n",
    "        self.__time_area = time_area\n",
    "        self.__company = company\n",
    "        \n",
    "    def display(self):\n",
    "        print(\"{}\\t{}\\t{}\".format(self.name, self.__sex, self.__age))\n",
    "        print(\"{}\\t{}\".format(self.__time_area, self.__company))\n",
    "    \n",
    "def main():\n",
    "    resume_a = Resume(\"鸣人\")\n",
    "    resume_a.set_personal_info(\"男\", \"29\")\n",
    "    resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n",
    "    \n",
    "    resume_b = Resume(\"鸣人\")\n",
    "    resume_b.set_personal_info(\"男\", \"29\")\n",
    "    resume_b.set_work_experience(\"2016-2018\", \"木叶公司\")\n",
    "    \n",
    "    resume_c = Resume(\"鸣人\")\n",
    "    resume_c.set_personal_info(\"男\", \"29\")\n",
    "    resume_c.set_work_experience(\"2016-2018\", \"木叶公司\")\n",
    "    \n",
    "    resume_a.display()\n",
    "    resume_b.display()\n",
    "    resume_c.display()\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "- 上述main函数中生成简历的方法，相当于手写简历，三份简历要三次实例化\n",
    "- 而且如果要更改某个字段，比如把时间从`2016-2018`改成`2017-2018`，那么同样修改三次\n",
    "\n",
    "那如果这样写呢？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "鸣人\t男\t29\n",
      "2016-2018\t木叶公司\n",
      "鸣人\t男\t29\n",
      "2016-2018\t木叶公司\n",
      "鸣人\t男\t29\n",
      "2016-2018\t木叶公司\n"
     ]
    }
   ],
   "source": [
    "def main():\n",
    "    resume_a = Resume(\"鸣人\")\n",
    "    resume_a.set_personal_info(\"男\", \"29\")\n",
    "    resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n",
    "    \n",
    "    resume_b = resume_a\n",
    "    \n",
    "    resume_c = resume_a\n",
    "    \n",
    "    resume_a.display()\n",
    "    resume_b.display()\n",
    "    resume_c.display()\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "- 这里传递的是引用，而不是具体的值，相当于在简历b和简历c上没有实际内容，而是写着“详见简历a”\n",
    "- 可以使用clone的方法解决这个问题，即原型模式\n",
    "\n",
    "## 原型模式\n",
    "\n",
    "原型模式，即用原型实例指定创建对象的种类，并且通过拷贝这些原型创建新的对象[DP]。也就是从一个对象再创建另外一个可定制的对象，而且不需要知道任何创建的细节。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "from copy import copy\n",
    "\n",
    "class Prototype():\n",
    "    \"\"\"\n",
    "    抽象原型类\n",
    "    \"\"\"\n",
    "    __metaclass__ = ABCMeta\n",
    "    def __init__(self, id):\n",
    "        self.id = id\n",
    "        \n",
    "    @abstractmethod\n",
    "    def clone(self):\n",
    "        pass\n",
    "\n",
    "class ConcretePrototypeOne(Prototype):\n",
    "    \"\"\"\n",
    "    具体原型类\n",
    "    \"\"\"\n",
    "    def __init__(self, id):\n",
    "        super().__init__(id)\n",
    "    \n",
    "    def clone(self):\n",
    "        return copy(self)  # 1. 浅拷贝copy.copy() 或 深拷贝copy.deepcopy() 2. Python无需强制类型转换\n",
    "\n",
    "def main():\n",
    "    prototype1 = ConcretePrototypeOne(\"1\")\n",
    "    prototype1_cloned = prototype1.clone()  \n",
    "    print(prototype1_cloned.id)\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Python中的浅拷贝与深拷贝\n",
    "\n",
    "Python中的对象之间赋值时是按引用传递的，如果需要拷贝对象，需要使用标准库中的copy模块。\n",
    "\n",
    "- copy.copy(浅拷贝):只拷贝顶层对象，不会拷贝顶层对象的内部的对象成员变量；\n",
    "- copy.deepcopy(深拷贝):拷贝对象及其子对象\n",
    "\n",
    "按照基础版本的简历类定义，成员变量的类型都是基本数据类型(string)，所以使用浅拷贝即可。那么什么时候用深拷贝呢？假如我们将工作经历定义为一个单独的类WorkExperience，那么简历类中就会有一个成员变量的类型是WorkExperience，如果这时候需要拷贝操作，就需要用深拷贝了。\n",
    "\n",
    "### 深拷贝原型模式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---浅拷贝, 工作经历都被修改成最后一次的值---\n",
      "鸣人\t男\t29\n",
      "2019-2020\t问问公司\n",
      "鸣人\t男\t29\n",
      "2019-2020\t问问公司\n",
      "鸣人\t男\t24\n",
      "2019-2020\t问问公司\n",
      "--------深拷贝, 工作经历为不同的值--------\n",
      "鸣人\t男\t29\n",
      "2016-2018\t木叶公司\n",
      "鸣人\t男\t29\n",
      "2018-2019\t王者学校\n",
      "鸣人\t男\t24\n",
      "2019-2020\t问问公司\n"
     ]
    }
   ],
   "source": [
    "from copy import deepcopy\n",
    "\n",
    "\n",
    "class WorkExperience():\n",
    "\n",
    "    def __init__(self, time_area=\"\", company=\"\"):\n",
    "        self.time_area = time_area\n",
    "        self.company = company\n",
    "        \n",
    "class Resume():\n",
    "    \n",
    "    def __init__(self, name):\n",
    "        self.name = name  # python默认成员变量公开\n",
    "        self.__sex = None  # python默认成员变量公开，加__表示私有\n",
    "        self.__age = None  # python默认成员变量公开，加__表示私有\n",
    "        self.__work_expereince = WorkExperience()  # python默认成员变量公开，加__表示私有\n",
    "        \n",
    "    def set_personal_info(self, sex, age):\n",
    "        self.__sex = sex\n",
    "        self.__age = age\n",
    "    \n",
    "    def set_work_experience(self, time_area, company):\n",
    "        self.__work_expereince.time_area = time_area\n",
    "        self.__work_expereince.company = company\n",
    "        \n",
    "    def display(self):\n",
    "        print(\"{}\\t{}\\t{}\".format(self.name, self.__sex, self.__age))\n",
    "        print(\"{}\\t{}\".format(self.__work_expereince.time_area, self.__work_expereince.company))\n",
    "        \n",
    "    def deep_clone(self):\n",
    "        \"\"\"\n",
    "        深拷贝方法\n",
    "        \"\"\"\n",
    "        return deepcopy(self)\n",
    "    \n",
    "    def clone(self):\n",
    "        \"\"\"\n",
    "        浅拷贝方法\n",
    "        \"\"\"\n",
    "        return copy(self)\n",
    "    \n",
    "\n",
    "def main():\n",
    "    resume_a = Resume(\"鸣人\")\n",
    "    resume_a.set_personal_info(\"男\", \"29\")\n",
    "    resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n",
    "    \n",
    "    resume_b = resume_a.clone()\n",
    "    resume_b.set_work_experience(\"2018-2019\", \"王者学校\")\n",
    "    \n",
    "    resume_c = resume_a.clone()\n",
    "    resume_c.set_personal_info(\"男\", \"24\")\n",
    "    resume_c.set_work_experience(\"2019-2020\", \"问问公司\")\n",
    "    \n",
    "    resume_a.display()\n",
    "    resume_b.display()\n",
    "    resume_c.display()\n",
    "    \n",
    "def deep_main():\n",
    "    resume_a = Resume(\"鸣人\")\n",
    "    resume_a.set_personal_info(\"男\", \"29\")\n",
    "    resume_a.set_work_experience(\"2016-2018\", \"木叶公司\")\n",
    "    \n",
    "    resume_b = resume_a.deep_clone()\n",
    "    resume_b.set_work_experience(\"2018-2019\", \"王者学校\")\n",
    "    \n",
    "    resume_c = resume_a.deep_clone()\n",
    "    resume_c.set_personal_info(\"男\", \"24\")\n",
    "    resume_c.set_work_experience(\"2019-2020\", \"问问公司\")\n",
    "    \n",
    "    resume_a.display()\n",
    "    resume_b.display()\n",
    "    resume_c.display()\n",
    "\n",
    "print(\"---浅拷贝, 工作经历都被修改成最后一次的值---\")\n",
    "main()\n",
    "print(\"--------深拷贝, 工作经历为不同的值--------\")\n",
    "deep_main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
